//
//  UIScrollView+QTEmpty.m
//  Pods
//
//  Created by 张俊博 on 16/8/23.
//
//

#import "UIScrollView+QTEmpty.h"
#import "QTCategoryMacro.h"
#import <objc/runtime.h>

QTSYNTH_DUMMY_CLASS(UIScrollView_QTEmpty)

#pragma mark - UIView+ConstraintBasedLayoutExtensions

@interface UIView (ConstraintBasedLayoutExtensions)

- (NSLayoutConstraint *)equallyRelatedConstraintWithView:(UIView *)view attribute:(NSLayoutAttribute)attribute;

@end

@implementation UIView (ConstraintBasedLayoutExtensions)

- (NSLayoutConstraint *)equallyRelatedConstraintWithView:(UIView *)view attribute:(NSLayoutAttribute)attribute
{
    return [NSLayoutConstraint constraintWithItem:view
                                        attribute:attribute
                                        relatedBy:NSLayoutRelationEqual
                                           toItem:self
                                        attribute:attribute
                                       multiplier:1.0
                                         constant:0.0];
}

@end

#pragma mark - EmptyDataView

@interface EmptyDataView : UIView

@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIView *customView;

- (void)setupConstraints;
- (void)prepareForReuse;

@end

@implementation EmptyDataView

- (instancetype)init
{
    self =  [super init];
    if (self) {
        [self addSubview:self.contentView];
    }
    return self;
}

- (void)didMoveToSuperview
{
    self.frame = self.superview.bounds;
    
    _contentView.alpha = 1.0;
}


#pragma mark - Getters

- (UIView *)contentView
{
    if (!_contentView)
    {
        _contentView = [UIView new];
        _contentView.translatesAutoresizingMaskIntoConstraints = NO;
        _contentView.backgroundColor = [UIColor clearColor];
        _contentView.userInteractionEnabled = YES;
        _contentView.alpha = 0;
    }
    return _contentView;
}

- (UIImageView *)imageView
{
    if (!_imageView)
    {
        _imageView = [UIImageView new];
        _imageView.translatesAutoresizingMaskIntoConstraints = NO;
        _imageView.backgroundColor = [UIColor clearColor];
        _imageView.contentMode = UIViewContentModeScaleAspectFit;
        _imageView.userInteractionEnabled = NO;
        _imageView.accessibilityIdentifier = @"empty set background image";
        
        [_contentView addSubview:_imageView];
    }
    return _imageView;
}

#pragma mark - Setters

- (void)setCustomView:(UIView *)view
{
    if (!view) {
        return;
    }
    
    if (_customView) {
        [_customView removeFromSuperview];
        _customView = nil;
    }
    
    _customView = view;
    _customView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.contentView addSubview:_customView];
}


#pragma mark - Action Methods

- (void)removeAllConstraints
{
    [self removeConstraints:self.constraints];
    [_contentView removeConstraints:_contentView.constraints];
}

- (void)prepareForReuse
{
    [self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    
    _imageView = nil;
    _customView = nil;
    
    [self removeAllConstraints];
}


#pragma mark - Auto-Layout Configuration

- (void)setupConstraints
{
    NSLayoutConstraint *centerXConstraint = [self equallyRelatedConstraintWithView:self.contentView attribute:NSLayoutAttributeCenterX];
    NSLayoutConstraint *centerYConstraint = [self equallyRelatedConstraintWithView:self.contentView attribute:NSLayoutAttributeCenterY];
    
    [self addConstraint:centerXConstraint];
    [self addConstraint:centerYConstraint];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:nil views:@{@"contentView": self.contentView}]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:nil views:@{@"contentView": self.contentView}]];
    
    if (_customView) {
        [self.contentView addSubview:_customView];
    } else {
        if (_imageView.superview) {
            [self.contentView addConstraint:[self.contentView equallyRelatedConstraintWithView:_imageView attribute:NSLayoutAttributeCenterX]];
            [self.contentView addConstraint:[self.contentView equallyRelatedConstraintWithView:_imageView attribute:NSLayoutAttributeCenterY]];
        }
    }
}

@end

#pragma mark - UIScrollView+EmptyDataSource

static char const * const kEmptyDataSource  =   "emptyDataSource";
static char const * const kEmptyDataView    =   "emptyDataView";

@interface UIScrollView () <UIGestureRecognizerDelegate>
@property (nonatomic, readonly) EmptyDataView *emptyDataView;
@end

@implementation UIScrollView (QTEmpty)

#pragma mark - Getters (Public)

- (id<EmptyDataSource>)emptyDataSource
{
    return objc_getAssociatedObject(self, kEmptyDataSource);
}

- (BOOL)isEmptyDataVisible
{
    UIView *view = objc_getAssociatedObject(self, kEmptyDataView);
    return view ? !view.hidden : NO;
}

#pragma mark - Getters (Private)

- (EmptyDataView *)emptyDataView
{
    EmptyDataView *view = objc_getAssociatedObject(self, kEmptyDataView);
    
    if (!view)
    {
        view = [EmptyDataView new];
        view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        view.hidden = YES;
        
        [self setEmptyDataView:view];
    }
    return view;
}

- (BOOL)canDisplay
{
//    if (self.emptyDataSource && [self.emptyDataSource conformsToProtocol:@protocol(EmptyDataSource)]) {
    if (self.emptyDataSource) {
        if ([self isKindOfClass:[UITableView class]]
            || [self isKindOfClass:[UICollectionView class]]
            || [self isKindOfClass:[UIScrollView class]]) {
            return YES;
        }
    }

    return NO;
}

- (NSInteger)itemsCount
{
    NSInteger items = 0;
    
    if (![self respondsToSelector:@selector(dataSource)]) {
        return items;
    }
    
    if ([self isKindOfClass:[UITableView class]]) {
        
        UITableView *tableView = (UITableView *)self;
        id <UITableViewDataSource> dataSource = tableView.dataSource;
        
        NSInteger sections = 1;
        
        if (dataSource && [dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
            sections = [dataSource numberOfSectionsInTableView:tableView];
        }
        
        if (dataSource && [dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) {
            for (NSInteger section = 0; section < sections; section++) {
                items += [dataSource tableView:tableView numberOfRowsInSection:section];
            }
        }
    } else if ([self isKindOfClass:[UICollectionView class]]) {
        
        UICollectionView *collectionView = (UICollectionView *)self;
        id <UICollectionViewDataSource> dataSource = collectionView.dataSource;
        
        NSInteger sections = 1;
        
        if (dataSource && [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
            sections = [dataSource numberOfSectionsInCollectionView:collectionView];
        }
        
        if (dataSource && [dataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]) {
            for (NSInteger section = 0; section < sections; section++) {
                items += [dataSource collectionView:collectionView numberOfItemsInSection:section];
            }
        }
    }
    
    return items;
}

#pragma mark - Data Source Getters

- (UIImage *)image
{
    if (self.emptyDataSource && [self.emptyDataSource respondsToSelector:@selector(imageForEmptyData:)]) {
        UIImage *image = [self.emptyDataSource imageForEmptyData:self];
        return image;
    }
    return nil;
}

- (UIColor *)dataBackgroundColor
{
    if (self.emptyDataSource && [self.emptyDataSource respondsToSelector:@selector(backgroundColorForEmptyData:)]) {
        UIColor *color = [self.emptyDataSource backgroundColorForEmptyData:self];
        return color;
    }
    return [UIColor clearColor];
}

- (UIView *)customView
{
    if (self.emptyDataSource && [self.emptyDataSource respondsToSelector:@selector(customViewForEmptyData:)]) {
        UIView *view = [self.emptyDataSource customViewForEmptyData:self];
        return view;
    }
    return nil;
}

#pragma mark - Setters (Public)

- (void)setEmptyDataSource:(id<EmptyDataSource>)datasource
{
    if (!datasource || ![self canDisplay]) {
        [self invalidate];
    }
    
    objc_setAssociatedObject(self, kEmptyDataSource, datasource, OBJC_ASSOCIATION_ASSIGN);
    
    [self swizzleIfPossible:@selector(reloadData)];
    
    if ([self isKindOfClass:[UITableView class]]) {
        [self swizzleIfPossible:@selector(endUpdates)];
    }
}

#pragma mark - Setters (Private)

- (void)setEmptyDataView:(EmptyDataView *)view
{
    objc_setAssociatedObject(self, kEmptyDataView, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)reloadEmptyData
{
    if (![self canDisplay]) {
        return;
    }
    
    if ([self itemsCount] == 0) {
        EmptyDataView *view = self.emptyDataView;
        
        if (!view.superview) {
            if (([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]]) && self.subviews.count > 1) {
                [self insertSubview:view atIndex:0];
            }
            else {
                [self addSubview:view];
            }
        }
        
        [view prepareForReuse];
        
        UIView *customView = [self customView];
        
        if (customView) {
            view.customView = customView;
        } else {
            UIImage *image = [self image];
            if (image) {
                view.imageView.image = image;
            }
        }
        
        view.backgroundColor = [self dataBackgroundColor];
        view.hidden = NO;
        view.clipsToBounds = YES;
        
        //        self.scrollEnabled = ([self customView]||[self image])?NO:YES;
        
        [view setupConstraints];
        
        [UIView performWithoutAnimation:^{
            [view layoutIfNeeded];
        }];
    } else if (self.isEmptyDataVisible) {
        [self invalidate];
    }
}

- (void)invalidate
{
    if (self.emptyDataView) {
        [self.emptyDataView prepareForReuse];
        [self.emptyDataView removeFromSuperview];
        
        [self setEmptyDataView:nil];
    }
    
    self.scrollEnabled = YES;
}

#pragma mark - Method Swizzling

static NSMutableDictionary *_impLookupTable;
static NSString *const SwizzleInfoPointerKey    = @"pointer";
static NSString *const SwizzleInfoOwnerKey      = @"owner";
static NSString *const SwizzleInfoSelectorKey   = @"selector";

void original_implementation(id self, SEL _cmd)
{
    Class baseClass = baseClassToSwizzleForTarget(self);
    NSString *key = implementationKey(baseClass, _cmd);
    
    NSDictionary *swizzleInfo = [_impLookupTable objectForKey:key];
    NSValue *impValue = [swizzleInfo valueForKey:SwizzleInfoPointerKey];
    
    IMP impPointer = [impValue pointerValue];
    
    [self reloadEmptyData];
    
    if (impPointer) {
        ((void(*)(id,SEL))impPointer)(self,_cmd);
    }
}

NSString *implementationKey(Class class, SEL selector)
{
    if (!class || !selector) {
        return nil;
    }
    
    NSString *className = NSStringFromClass([class class]);
    
    NSString *selectorName = NSStringFromSelector(selector);
    return [NSString stringWithFormat:@"%@_%@",className,selectorName];
}

Class baseClassToSwizzleForTarget(id target)
{
    if ([target isKindOfClass:[UITableView class]]) {
        return [UITableView class];
    }
    else if ([target isKindOfClass:[UICollectionView class]]) {
        return [UICollectionView class];
    }
    else if ([target isKindOfClass:[UIScrollView class]]) {
        return [UIScrollView class];
    }
    
    return nil;
}

- (void)swizzleIfPossible:(SEL)selector
{
    if (![self respondsToSelector:selector]) {
        return;
    }
    
    if (!_impLookupTable) {
        _impLookupTable = [[NSMutableDictionary alloc] initWithCapacity:3];
    }
    
    for (NSDictionary *info in [_impLookupTable allValues]) {
        Class class = [info objectForKey:SwizzleInfoOwnerKey];
        NSString *selectorName = [info objectForKey:SwizzleInfoSelectorKey];
        
        if ([selectorName isEqualToString:NSStringFromSelector(selector)]) {
            if ([self isKindOfClass:class]) {
                return;
            }
        }
    }
    
    Class baseClass = baseClassToSwizzleForTarget(self);
    NSString *key = implementationKey(baseClass, selector);
    NSValue *impValue = [[_impLookupTable objectForKey:key] valueForKey:SwizzleInfoPointerKey];
    
    if (impValue || !key || !baseClass) {
        return;
    }
    
    Method method = class_getInstanceMethod(baseClass, selector);
    IMP newImplementation = method_setImplementation(method, (IMP)original_implementation);
    
    NSDictionary *swizzledInfo = @{SwizzleInfoOwnerKey: baseClass,
                                   SwizzleInfoSelectorKey: NSStringFromSelector(selector),
                                   SwizzleInfoPointerKey: [NSValue valueWithPointer:newImplementation]};
    
    [_impLookupTable setObject:swizzledInfo forKey:key];
}

@end
